## appendChild 注意。

appendChild 添加已有节点是移动。

```html
<body>
  <ul id="ul1">
    <li id="a">a</li>
  </ul>
  <ul id="ul2">
    <li id="b">b</li>
  </ul>
  <script>
    ul2.appendChild(a);
  </script>
</body>
```

- 结果如下

```html
<body>
  <ul id="ul1"></ul>
  <ul id="ul2">
    <li id="b">b</li>
    <li id="a">a</li>
  </ul>
</body>
```

## 类型转换的两种方式

- 强制：parseInt， parseFloat，toString 等

- 隐式：== ,＋拼接宇符串

## call、apply、bind 的区别

```js
let name = "李四";
let obj = {
  name: "张三",
};

function fn(arg1, arg2) {
  console.log(this.name, arg1, arg2);
}

fn.apply(obj, [1, 2]); // apply 参数是数组
fn.call(obj, 3, 4); // call 参数是逐个传递
let fn2 = fn.bind(obj); // 注意bind会返回一个新函数，这里需要接收一下
fn2(5, 6);

// bind() 方法创建一个新的函数，在 bind() 被调用时，这个新函数的 this 被指定为 bind() 的第一个参数，而其余参数将作为新函数的参数，供调用时使用。
```

## 类型判断

- 判断简单类型

```js
typeof "abc"; // string
typeof function () {}; // function
```

- 判断数组

```js
//  第一种 instanceof

const a = [];
const b = {};
console.log(a instanceof Array); //true
console.log(a instanceof Object); //true,在数组的原型链上也能找到Object构造函数
```

```js
// 第二种 Array.isArray();

Array.isArray([]);
```

```js
// 第三种 通过 constructor 判断
console.log([].constructor === Array); //true

console.log({}.constructor === Array); //false
console.log({}.constructor === Object); //true

// constructor 可进行更改，有隐患
let obj = {};
obj.constructor = [].constructor;

console.log(obj.constructor === Array); //true
```

```js
// 第四种
// 通过isPrototypeOf()方法判断
// 从原型入手，Array.prototype 属性表示 Array 构造函数的原型，
// 其中有一个方法是 isPrototypeOf() 用于测试一个对象是否存在于另一个对象的原型链上。

console.log(Array.prototype.isPrototypeOf([])); //true
```

```js
// 第五种
// 通过Object.getPrototypeOf方法判断
// Object.getPrototypeOf() 方法返回指定对象的原型，所以只要跟Array的原型比较即可。

console.log(Object.getPrototypeOf([]) === Array.prototype); // true
```

```js
// 第六种
// 通过Object.prototype.toString.call()判断
// 虽然Array也继承自Object，但js在Array.prototype上重写了toString，而我们通过toString.call(arr)实际上是通过原型链调用了。可以获取到变量的不同类型。

console.log(Object.prototype.toString.call([]) === "[object Array]"); //true

console.log(Object.prototype.toString.call([]).slice(8, -1) === "Array"); //true

console.log(Object.prototype.toString.call(arr).indexOf("Array") !== -1); //true
```

- 判断对象

```js
// 第一种
Object.prototype.toString.call(obj) === "[object Object]";
```

```js
// 第二种
obj.constructor === Object;
```

```js
// 第三种
obj instanceof Object;
```

```js
// 第四种 区分obj 和 null
console.log(typeof obj === "object" && obj != null);
```

- instanceof
  用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

## 何时使用 === 何时使用 ==

```js
let str = "abc",
  num = 123;
if (str === num) {
} // 这里应该使用 ===
if (str == null) {
} // 在不确定值类型是null 或者 undefined 时应该使用 ==
```

## 函数声明和函数表达式的区别

函数声明 function fn(){}  
函数表达式 const fn2 = function(){}  
函数声明会在代码执行前预加载，而函数表达式不会

```js
fn();
fn2(); // Uncaught ReferenceError: Cannot access 'fn2' before initialization

function fn() {}
const fn2 = function () {};
```

## 获取 url 参数的常见方法

- split 拆分法

```js
console.log(getQuery("id"));

function getQuery(variable) {
  debugger;
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return pair[1];
    }
  }
  return false;
}
```

- 正则匹配

```js
console.log(getQuery("id"));
function getQuery(name) {
  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  let r = window.location.search.substr(1).match(reg);
  if (r != null) {
    return decodeURIComponent(r[2]);
  }
  return null;
}
```

- URL

```js
var urlId = new URL(window.location).searchParams.get("id");
```

- [URLSearchParams](https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams/URLSearchParams) (截止到 2022 年 01 月还处于实验阶段)

```js
const params = new URLSearchParams(window.location.search);
console.log(params.get("id"));
```

## window.onload 和 DOMContentLoaded 的区别

```js
window.addEventListener("load", function () {
  // 页面的全部资源加载完才会执行，包括图片、视频等
});

document.addEventListener("DOMContentLoaded", function () {
  // DOM 渲染完即可执行，此时图片、视频还可能没有加载完
});
```

## var 和 let 和 const 的区别

var 是 ES5 语法，let const 是 ES6 语法；var 有变量提升;  
var 和 let 是变量，可修改；const 是常量，不可修改，const 对比的变量地址类型；  
let const 有块级作用域，var 没有;

## HTML 块级元素和内联元素有哪些？

display: block/table; 有 div hl h2 table ul ol p 等  
display: inline/inline-block;有 span img input button 等

## 0 点 1 加 0 点 2 不等于 0 点 3

1. 计算机使用二进制存储数据
2. 整数转换二进制没有误差，如 9 转换为二进制是 1001
3. 而小数可能无法用二进制准确表达，如 0.2 转换为 0.0011001100110011 (超出计算精度，结果保留十六位小数)
4. 不光 JS，其他编程语言也都一样

## for_in 和 for_of 的区别

- for of 主要是遍历 Symbol.iterator 对象。
- for of 也可用于异步的遍历

```js
function muti(num) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * num);
    }, 1000);
  });
}

const nums = [1, 2, 3];

!(async function () {
  for (let k of nums) {
    const res = await muti(k);
    console.log(res);
  }
})();

nums.forEach(async (i) => {
  const res = await muti(i);
  console.log(res);
});
```

- for in 主要是遍历(对象，数组等可遍历的同步代码)

## 容易混淆之 property 和 attribute 区别

attribute 属性返回该元素所有属性节点的一个实时集合  
property：JavaScript 属性是对象的特征，通常描述与数据结构相关的属性。

<!-- - property：修改对象属性，不会体现到 html 结构中
- attribute：修改 html 属性 ，会改变 html 结构
- 两者都有可能引起 DOM 重新渲染 -->

```html
<body id="body" class="body" data-title="title">
  <script>
    console.dir(body);
  </script>
</body>
```

可以发现有一个名为“attributes”的属性，类型是 NamedNodeMap，同时有“id”和“className”、但没有“data-title”这个自定义的属性。  
每一个 DOM 对象都会有它默认的基本属性，而在创建的时候，它只会创建这些基本属性，我们在自定义的属性是不会直接放到 DOM 中的而是放在 attributes 属性中。  
从这里就可以看出，attributes 是属于 property 的一个子集。

- attribute 赋值 与 取值

```js
document.body.setAttribute("class", "test");
console.log(document.body.attributes);
console.log(document.body.getAttribute("title"));
```

- property 赋值 与 取值

```js
body.className = "xxx";
body.id = "xxx";
const className = body.className;
```
:::tip
“class”变成 Property 之后叫做“className”，因为“class”是 ECMA 的关键字。
:::

## 词法作用域 vs 动态作用域

```js
var value = 1;

function foo() {
  console.log(value);
}

function bar() {
  var value = 2;
  foo();
}

bar();

// 结果是 ???
```

假设 JavaScript 采用静态作用域，让我们分析下执行过程：

执行 foo 函数，先从 foo 函数内部查找是否有局部变量 value，如果没有，就根据书写的位置，查找上面一层的代码，也就是 value 等于 1，所以结果会打印 1。

假设 JavaScript 采用动态作用域，让我们分析下执行过程：

执行 foo 函数，依然是从 foo 函数内部查找是否有局部变量 value。如果没有，就从调用函数的作用域，也就是 bar 函数内部查找 value 变量，所以结果会打印 2。

前面我们已经说了，JavaScript 采用的是静态作用域，所以这个例子的结果是 1。

## pnpm 、 npm、cnpm、yarn 的区别
npm, cnpm, yarn 和 pnpm 都是 JavaScript 包管理器，用于下载、安装、更新和管理 JavaScript 模块。它们之间的区别如下：

- npm (Node Package Manager) 是官方的 JavaScript 包管理器，它可以从全球范围内的npm仓库下载包。
- cnpm 是 npm 的中国镜像，它使用淘宝镜像来加速包的下载速度，因为在中国使用npm下载速度通常比较慢。
- yarn 是 Facebook 开发的包管理器，它被设计为比 npm 更快、更稳定、更安全，能够并行下载依赖项并使用缓存，以便加速安装和更新依赖项。
- pnpm 是一个快速、节省空间、可共享依赖项的包管理器，它在本地创建符号链接，可以同时使用相同的依赖项来安装不同的项目，减少了磁盘空间的占用和重复下载依赖项的问题。
总的来说，它们都有相似的功能，但在性能、稳定性、缓存管理、磁盘空间使用、并发性等方面略有不同，选择使用哪个包管理器取决于个人或团队的需求和偏好。

## JS 面向对象编程
面向对象编程（Object-oriented programming，OOP）是一种编程范式，它是一种将现实世界的概念和行为转换为计算机程序的方法。面向对象编程将数据和操作数据的函数组合在一起，形成对象。对象是一种封装了数据和对数据进行操作的方法的实体。

在面向对象编程中，对象是程序的基本单元，可以通过类或原型来创建和定义对象。类或原型是对象的模板，它定义了对象的属性和方法。属性是对象的数据，方法是操作数据的函数。对象可以相互交互，实现数据共享和通信。

面向对象编程有许多优点，例如：

- 抽象化：面向对象编程能够将现实世界的复杂问题抽象化为简单的对象和类。
- 封装性：面向对象编程通过封装实现了数据的保护，可以隐藏对象的内部实现细节，使得代码更加模块化、可维护和可复用。
- 继承性：面向对象编程通过继承实现了代码的复用，可以通过扩展已有的类或原型来创建新的类或原型。
- 多态性：面向对象编程通过多态实现了代码的灵活性和可扩展性，可以通过不同的方法实现相同的行为，使得代码更加通用和可扩展。
- 面向对象编程是现代编程语言中最常用的编程范式之一，常见的面向对象编程语言包括 Java、Python、C++、C#、JavaScript 等。



## JS 模块化详解
在JavaScript中，模块化可以让我们将代码按照一定的逻辑划分成不同的模块，从而实现代码的复用、分离和封装。目前主要有以下几种JS模块化方案：

- CommonJS：CommonJS是一种用于服务器端的模块化规范，其主要特点是采用同步加载模块的方式。在CommonJS规范中，每个模块都有自己独立的作用域，可以定义自己的变量和方法，同时可以通过module.exports对象来导出模块接口，通过require函数来引入其他模块的接口。
- AMD：AMD是一种用于浏览器端的异步模块化规范，其主要特点是采用异步加载模块的方式。在AMD规范中，通过define函数来定义模块，可以指定依赖关系和导出接口，同时通过require函数来异步加载其他模块。
- ES6模块化：ES6模块化是JavaScript官方的模块化规范，其主要特点是采用静态导入和导出模块的方式。在ES6模块化中，使用import语句来导入其他模块的接口，使用export语句来导出自己的接口，同时可以使用default关键字来指定默认导出的接口。

ES6模块化相对于其他模块化方案来说具有以下优点：

- 支持静态分析：由于ES6模块化是静态导入和导出的，因此可以在编译时进行静态分析和优化，从而提高了代码的运行效率。
- 支持循环引用：ES6模块化支持循环引用，可以解决其他模块化方案中的循环引用问题。
- 支持Tree Shaking：由于ES6模块化是静态的，因此可以使用Tree Shaking算法来删除无用的代码，从而减小打包后的文件大小。

综上所述，ES6模块化是目前推荐使用的JavaScript模块化方案，它具有更好的性能、可维护性和可靠性。

## JavaScript 性能优化
JavaScript 性能优化是提高 JavaScript 应用程序性能的过程，主要包括以下方面：

- 减少 HTTP 请求：减少页面中的文件数和文件大小，可以减少页面的加载时间。可以通过合并和压缩 JavaScript、CSS 和图片等文件，减少 HTTP 请求的数量。
- 减少 DOM 操作：DOM 操作是非常昂贵的，因为它们涉及到重新渲染页面的一部分。可以通过批量操作 DOM 元素、缓存 DOM 查询的结果等方式来减少 DOM 操作。
- 避免使用全局变量：全局变量会增加命名冲突的可能性，还会使代码难以维护。可以使用闭包、命名空间等方式来避免使用全局变量。
- 优化循环：循环是一种常见的性能瓶颈。可以使用遍历数组的方式来避免使用 for 循环，还可以使用惰性求值、缓存变量等方式来优化循环。
- 避免不必要的计算：在代码中避免不必要的计算，可以减少计算量，提高性能。
- 使用事件委托：事件委托可以减少事件处理程序的数量，从而提高性能。
- 避免使用 eval() 函数：eval() 函数可以动态地执行代码，但它会使代码变得难以调试和维护。
- 使用 Web Workers：Web Workers 可以在后台运行 JavaScript 代码，从而提高页面的响应速度。
- 避免使用 with 语句：with 语句会使代码难以维护，因为它会改变作用域链。
- 使用定时器：使用定时器可以使代码异步执行，从而提高页面的响应速度。
- 使用缓存：可以使用缓存来存储经常访问的数据，从而避免重复计算。
- 去掉重复的代码：重复的代码会增加代码量，也会使代码难以维护。
- 压缩 JavaScript 代码：可以使用 JavaScript 压缩器来压缩 JavaScript 代码，从而减少文件大小，提高页面的加载速度。
总的来说，JavaScript 性能优化需要结合具体的业务场景来进行，需要在代码中注意细节，从而提高代码的执行效率。

## GC 垃圾回收
垃圾回收（Garbage Collection）是指在计算机程序执行过程中，自动回收不再使用的内存空间的一种机制。在 JavaScript 中，垃圾回收主要针对不再被引用的对象。

JavaScript 中的垃圾回收机制使用的是自动垃圾回收，也就是说开发者不需要手动进行内存管理，而是由 JavaScript 引擎自动回收不再使用的对象，从而避免了内存泄漏等问题。

- JavaScript 中的垃圾回收机制主要分为两种：
  - 标记清除垃圾回收（Mark and Sweep Garbage Collection）：标记清除垃圾回收是一种常用的垃圾回收机制，它通过标记不再使用的对象，然后清除这些对象来回收内存空间。具体来说，垃圾回收机制会从全局变量开始遍历所有的对象，标记所有被引用的对象，然后清除所有未被标记的对象。这种垃圾回收机制可以处理循环引用的情况。
  - 引用计数垃圾回收（Reference Counting Garbage Collection）：引用计数垃圾回收是一种简单的垃圾回收机制，它通过计算一个对象被引用的次数来判断是否需要回收。当一个对象被引用时，计数器加 1；当一个对象不再被引用时，计数器减 1。当计数器为 0 时，该对象将被回收。但引用计数垃圾回收机制无法处理循环引用的情况，因此在现代的 JavaScript 引擎中已经不再使用。  
JavaScript 引擎的垃圾回收机制是自动的，但我们可以通过一些技巧来优化 JavaScript 应用的内存使用。比如，我们可以通过及时释放不再使用的变量，尽可能避免创建过多的临时变量，以及使用对象池等方式来优化内存使用。

## 内存管理
内存管理是指对计算机内存进行管理，包括分配、使用、释放等操作。在编程中，内存管理是一项重要的任务，特别是在一些语言中需要手动进行内存管理，例如 C/C++。而在现代的高级编程语言中，内存管理已经越来越被封装，使得程序员不需要手动进行内存管理，从而减少了程序错误和漏洞的可能性。

在 JavaScript 中，内存管理主要是由 JavaScript 引擎自动进行，开发者无需手动进行内存管理，但也需要注意一些内存使用的细节问题，例如循环引用、内存泄漏等问题。

JavaScript 中的内存管理主要涉及以下几个方面：

- 声明变量时，变量将被存储在栈内存或堆内存中。
- 原始类型的数据（如数值、布尔值、字符串等）会被存储在栈内存中，而引用类型的数据（如对象、数组、函数等）则会被存储在堆内存中。
- 对象的属性和方法是存储在堆内存中的，而变量则存储了该对象在堆内存中的引用。
- 当一个变量不再被引用时，JavaScript 引擎会自动回收其占用的内存空间。
在 JavaScript 中，由于开发者无需手动进行内存管理，因此在编写代码时需要特别注意内存使用的细节，避免出现内存泄漏等问题。比如，我们可以及时释放不再使用的变量，避免创建过多的临时变量，使用对象池等方式来优化内存使用。

## 了解函数式编程
函数式编程是一种编程范式，它是一种将计算机程序看作数学函数的计算，通过使用函数来组合代码，实现程序逻辑的抽象和复用。与面向对象编程不同的是，函数式编程更加强调函数的纯粹性和不可变性，即函数对同一输入始终产生同一输出，不对外部环境造成副作用。

- 函数式编程的主要特点包括：
  - 函数是一等公民：函数可以作为参数传递给其他函数，也可以作为返回值返回。
  - 不可变性：函数式编程中，变量的值不会随着程序的运行而改变，一旦赋值就无法改变，只能通过创建新的变量来实现状态的改变。
  - 纯函数：纯函数是指函数在相同的输入下，始终返回相同的输出，且不对外部环境造成副作用。
  - 高阶函数：函数式编程支持高阶函数，即可以接收其他函数作为参数或者返回一个函数作为结果的函数。
  - 组合函数：函数式编程中，可以将多个函数组合起来，构成一个新的函数，以实现程序的复用和抽象。
函数式编程的优点包括：可重用性高、代码量少、易于理解和调试、易于测试和并行化处理等。在 JavaScript 等编程语言中，函数式编程已经成为了一种常用的编程范式，通过使用高阶函数、箭头函数、函数组合等特性来实现函数式编程。

## 解构、rest 操作符的原理
JavaScript 中的对象和数组解构以及 rest 操作符都是 ES6 新增的语法，它们的原理如下：
### 对象解构
对象解构是一种将对象的属性值赋值给变量的方法，它的原理是根据解构表达式中的属性名，在对象中查找对应的属性值，然后将其赋值给对应的变量。例如：
```js
const person = { name: 'Alice', age: 30 };
const { name, age } = person;
console.log(name); // 'Alice'
console.log(age); // 30

```
在上面的代码中，我们通过对象解构的方式将 person 对象中的 name 和 age 属性分别赋值给了 name 和 age 变量。

### 数组解构
数组解构是一种将数组元素赋值给变量的方法，它的原理是根据解构表达式中的位置，依次获取数组中对应位置的元素，并将其赋值给对应的变量。例如：
```js 
const numbers = [1, 2, 3];
const [a, b, c] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

```
在上面的代码中，我们通过数组解构的方式将 numbers 数组中的前三个元素分别赋值给了 a、b 和 c 变量。

### Rest 操作符
Rest 操作符可以将数组或对象中的剩余元素转化为一个数组或对象，其原理是将剩余的元素打包成一个数组或对象。在对象中，它的语法是 "...变量名"，在数组中，它的语法是 "...变量名"。例如：
```js 
const person = { name: 'Alice', age: 30, address: 'New York' };
const { name, ...rest } = person;
console.log(name); // 'Alice'
console.log(rest); // { age: 30, address: 'New York' }

const numbers = [1, 2, 3, 4, 5];
const [a, b, ...rest] = numbers;
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

```
在上面的代码中，我们使用 Rest 操作符将 person 对象中除了 name 属性以外的属性打包成了一个对象，将 numbers 数组中除了前两个元素以外的元素打包成了一个数组。

总的来说，对象和数组的解构以及 Rest 操作符都是通过模式匹配的方式来进行赋值和打包操作的。在对象和数组解构中，模式匹配是基于对象或数组的属性名或索引位置，而在 Rest 操作符中，模式匹配是基于剩余的元素。

## 手写函数模拟 Object.entries
Object.entries() 是 JavaScript 中一个常用的方法，它返回一个给定对象自身可枚举属性的键值对数组。如果你想手动模拟这个方法的行为，可以使用 for...in 循环遍历对象的所有可枚举属性，并将它们作为键值对放入一个新数组中，示例如下：
```js 
function entries(obj) {
  let result = [];
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result.push([key, obj[key]]);
    }
  }
  return result;
}

```
这个函数接收一个对象作为参数，遍历对象的可枚举属性，将每个属性及其对应的值作为一个数组放入结果数组中，最后返回结果数组。需要注意的是，在 for...in 循环中，我们使用了 hasOwnProperty() 方法来判断属性是否是对象自身的属性，避免遍历到原型链上的属性。
使用这个函数可以得到与 Object.entries() 相同的结果：
```js 
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
console.log(entries(obj)); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]

```

## devOps 
DevOps（Development and Operations）是一种软件开发流程和文化的理念，旨在通过整合开发（Development）和运维（Operations）的流程和工具来加速软件交付和部署。它将开发团队、测试团队、运维团队等各个环节的人员和工具进行整合，以实现快速高效地部署、迭代和优化软件应用。

DevOps 的核心价值在于实现持续集成（Continuous Integration）、持续交付（Continuous Delivery）和持续部署（Continuous Deployment），即通过自动化工具和流程来实现软件的快速交付和部署，从而满足客户需求、提高产品质量和竞争力。

DevOps 的实现需要配合使用一系列的工具和技术，如自动化测试、版本控制、持续集成、持续部署、容器化等。同时，它也需要一种协作和沟通的文化，强调开发和运维之间的协作和交流，以实现高效的软件交付和运维管理。

在现代的软件开发和运维中，DevOps 已经成为一种不可或缺的流程和文化，帮助企业更好地应对不断变化的市场需求和竞争压力。

## AST 简介及常见AST库解析
### AST简介 

AST是抽象语法树（Abstract Syntax Tree）的缩写，是编程语言的一种中间表示，通常是一种树形结构，用于表示编程语言的抽象语法结构。

在前端开发中，AST可以被用于代码静态分析、优化和转换等方面。例如，在JavaScript中，可以使用AST将代码解析成一种抽象的语法树结构，然后对这个结构进行操作和转换，例如通过遍历AST节点实现代码检查和风格约束，或者通过修改AST节点来实现代码转换和优化，例如将ES6代码转换为ES5代码，或者将React JSX代码转换为普通的JavaScript代码等。

AST可以帮助开发者更好地理解和操作代码，同时也为代码自动化工具提供了有力的支持，例如在编译、打包和测试等方面，都可以通过AST来实现更加高效和可靠的代码处理和转换。
## map 与 set  

### [map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map)

Map 对象保存键值对，并且能够记住键的原始插入顺序。任何值（对象或者基本类型）都可以作为一个键或一个值。

- map 与 object 的区别

|              |                         map                          | object                                                                               |
| ------------ | :--------------------------------------------------: | ------------------------------------------------------------------------------------ |
| 键覆盖问题   |          如果有键和 map 原型同名，不会覆盖           | 则会覆盖                                                                             |
| 键值顺序     |               Map 按插入顺序保留键值对               | 在 Object 中，键必须是字符串或符号                                                   |
| 键值类型     | Map 中，键可以是任何类型，包括函数、对象和基本类型。 | 则会覆盖                                                                             |
| Size         |    Map 的键值对个数可以轻易地通过 size 属性获取。    | Object 的键值对个数只能手动计算。                                                    |
| 迭代         |       Map 是 可迭代的 的，所以可以直接被迭代。       | Object 没有实现 迭代协议，所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象。 |
| 性能         |          在频繁增删键值对的场景下表现更好。          | 在频繁添加和删除键值对的场景下未作出优化。                                           |
| 序列化和解析 |            没有元素的序列化和解析的支持。            | 原生的由 Object 到 JSON 的序列化支持，使用 JSON.stringify()。                        |

如果需要存储任意数量、任意类型的键值对，并且需要按照插入顺序保留这些键值对，那么使用 Map 可能更好。而如果需要存储一组键值对，其中键是字符串或符号，并且不需要按照特定的顺序访问它们，那么使用 Object 可能更好。

### [set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set)

Set 对象允许你存储任何类型的唯一值(元素只会出现一次)，无论是原始值或者是对象引用。  
set 是以 [value]的形式储存元素，字典 是以 [key：value] 的形式储存

- set 与 array 的区别

|              |                                         map                                          | array                                                                          |
| ------------ | :----------------------------------------------------------------------------------: | ------------------------------------------------------------------------------ |
| 元素的唯一性 |                    Set 中的元素是唯一的，即相同的元素只会出现一次                    | 而 Array 中可以包含重复的元素。                                                |
| 访问元素     |        Set 中没有下标这个概念，只能通过迭代器或转换成数组后通过索引来访问元素        | 而 Array 可以使用下标来访问元素。                                              |
| 排序和顺序   |                           Set 的元素没有顺序，无法进行排序                           | 而 Array 的元素是有顺序的，可以使用 sort 方法进行排序。                        |
| 性能         | Set 通常比 Array 快，特别是在需要对唯一性进行频繁检查时，因为 Set 是用哈希表实现的。 |                                                                                |
| 功能         |                        Set 只有基本的添加、删除、迭代等方法。                        | Array 拥有更多的方法和操作，例如 push、pop、splice、slice、concat、reduce 等等 |

如果需要存储元素的唯一性，并且不需要顺序和排序功能，使用Set可能更好。如果需要访问元素的顺序、排序功能，或者需要使用更多的数组操作，使用Array可能更好。当然，这两者并不是互斥的，可以根据具体的需求选择使用哪一个或者同时使用。
## WeakMap 和 WeakSet
在 JavaScript 中，WeakSet 和 WeakMap 是两种特殊的集合类型，它们与普通的 Set 和 Map 类型的主要区别在于，它们只能存储弱引用的对象作为键或值，而不会阻止垃圾回收器回收这些对象。
### WeakMap
WeakMap对象也是键值对的集合。它的键必须是对象类型，值可以是任意类型。它的键被弱保持，也就是说，当其键所指对象没有其他地方引用的时候，它会被 GC 回收掉。WeakMap提供的接口与Map相同。  
与Map对象不同的是，WeakMap的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态，是不可预知的。

### WeakSet
WeakSet对象是一组对象的集合。WeakSet中的对象不重复且不可枚举。  

与Set对象的主要区别有：

- WeakSets中的值必须是对象类型，不可以是别的类型
- WeakSet的“weak”指的是，对集合中的对象，如果不存在其他引用，那么该对象将可被垃圾回收。于是不存在一个当前可用对象组成的列表，所以WeakSets不可枚举

### 使用场景
JavaScript 中的 WeakSet 和 WeakMap 主要用于需要动态添加和删除元素的场景，以及在需要存储和管理对象生命周期的场景中。下面是一些具体的使用场景：
- 缓存数据：当需要缓存一些数据，但不希望这些数据成为内存泄漏的潜在源时，可以使用 WeakMap。通过将对象存储在 WeakMap 中，当这些对象不再需要时，它们将被垃圾回收器自动回收。这在 Web 应用程序中经常使用，例如缓存 DOM 节点或其他资源。
- 对象绑定：当需要将对象绑定到另一个对象上，但又不希望这些对象的生命周期与主对象绑定在一起时，可以使用 WeakMap。这在事件监听器或订阅者模式中非常有用，可以确保当绑定的对象不再需要时，它们将被自动删除。
- 数据结构：当需要动态添加和删除元素时，可以使用 WeakSet 和 WeakMap。例如，可以将一些临时对象存储在 WeakSet 中，以确保当它们不再需要时，它们将被自动删除。
需要注意的是，由于 WeakSet 和 WeakMap 中的键和值都是弱引用，所以它们不适合存储基本数据类型，例如数字或字符串等。


## 内存泄露
内存泄漏指的是程序中存在一些对象或数据占用了内存空间，但是这些对象或数据已经不再需要使用，却没有被垃圾回收机制回收。这导致内存空间被一直占用，最终导致内存耗尽。

JavaScript 程序中的内存泄漏通常是因为在代码中存在一些无用的对象或变量没有被及时释放。以下是几个常见的 JavaScript 内存泄漏的情况：

- 全局变量：在全局作用域中定义的变量会一直存在于内存中，除非页面卸载或浏览器关闭。如果定义了过多的全局变量，可能会导致内存不足。
- 闭包：闭包是一个函数和它所引用的变量的组合。如果一个函数中创建了一个闭包，并将其存储在一个全局变量或其他长期存在的地方，它会一直占用内存空间，直到闭包不再需要。
- 定时器和回调函数：定时器和回调函数是 JavaScript 中经常使用的功能。但是，如果定时器和回调函数中使用了不必要的变量或对象，这些变量和对象会一直存在于内存中，直到定时器和回调函数被取消或页面关闭。
- DOM 对象：在 JavaScript 中，DOM 对象通常是非常占用内存的。如果在页面中创建了大量的 DOM 对象，并且没有及时删除它们，可能会导致内存泄漏。
- 为避免 JavaScript 中的内存泄漏问题，可以采取以下措施：
  - 及时清理不再使用的变量和对象；
  - 避免使用全局变量；
  - 避免创建不必要的闭包；
  - 取消定时器和回调函数；
  - 及时删除不再需要的 DOM 对象。

## GET 和 POST 的区别

- get 参数通过 url 传递，post 放在 request body 中
- get 请求在 url 中传递的参数是有长度限制的，而 post 没有
- post 比 get 更安全，因为 get 参数都暴漏在 url 中，所以不能用来传递敏感信息
- get 请求只能进行 url 编码，而 post 支持多种编码方式
- get 请求会浏览器主动 cache，而 post 支持多种编码方式
- get 请求参数会被完整保留在浏览器历史记录里，而 post 中的参数不会被保留
- get 和 post 本质上就是 TCP 链接，并无差别，但由于 HTTP 的规定和浏览器/服务器的限制，导致他们在应用过程中体现出一些不同
- get 产生一个 TCP 数据包；post 产生两个




## 普通函数，箭头函数的区别

箭头函数没有原型，原型是 undefined
箭头函数 this 指向全局对象，而函数指向引用对象
call，apply，bind 方法改变不了箭头函数的指向


## JS中执行上下文是什么？

在 JavaScript 中，执行上下文（Execution Context）是指代码被执行时所在的环境。它是一个抽象概念，用于描述代码在运行时所处的状态，包括变量、函数、作用域、this 等信息。

执行上下文分为三种类型：
  - 全局执行上下文：在代码执行之前，JavaScript 引擎会先创建一个全局执行上下文，并将其压入执行栈中。全局执行上下文只有一个，并在整个代码执行过程中都存在，直到程序运行结束。
  - 函数执行上下文：每当一个函数被调用时，都会创建一个新的函数执行上下文。函数执行上下文保存了函数内部的变量、参数和this等信息，然后将其压入执行栈中。
  - Eval 执行上下文：在使用eval函数时，会创建一个新的执行上下文。

每个执行上下文都有自己的作用域链、变量对象、this 指向等特性。作用域链决定了变量和函数的可访问性，变量对象保存了当前执行上下文中的变量、函数等信息，this 指向当前执行上下文所属的对象。当一个函数执行完成后，它的执行上下文会从执行栈中弹出，程序会继续执行上一个执行上下文中的代码。
了解执行上下文的概念和特性对于理解 JavaScript 的作用域、闭包、this 等重要概念具有重要的帮助。同时，JavaScript 的执行上下文机制也为开发者提供了更为灵活和高效的编程方式，让我们能够编写出更加复杂和高效的代码。


## js中DOM 和 BOM是什么？
在JavaScript中，DOM和BOM是两个不同的概念。

DOM（Document Object Model）是文档对象模型的缩写，它定义了用于访问和操作HTML和XML文档的标准编程接口。通过DOM，JavaScript可以访问和修改文档中的元素、属性、文本等内容，从而实现动态地改变网页内容和结构。DOM树是文档的层次结构，由一系列的节点组成，包括文档节点、元素节点、文本节点等。

BOM（Browser Object Model）是浏览器对象模型的缩写，它提供了访问和操作浏览器窗口及其各个部分的接口，包括访问浏览器窗口尺寸、位置、历史记录、浏览器前进后退功能等。BOM的核心对象是window对象，它代表了整个浏览器窗口，同时也是JavaScript的全局对象，所有的全局变量和函数都是window对象的属性和方法。

在JavaScript中，DOM和BOM是紧密相关的概念，常常一起使用。通过DOM，可以访问和修改网页的内容和结构，通过BOM，可以实现浏览器的各种功能，如弹出对话框、获取用户输入、控制浏览器窗口等。理解DOM和BOM的概念和使用方法，对于JavaScript开发具有重要意义。

## 异步编程是什么？
异步编程是指在执行某个操作时，如果该操作需要等待某些操作的完成或者是需要从网络或磁盘中读取数据，就会出现一段等待时间。传统的同步编程方式会在等待时间中阻塞程序的执行，使得整个程序的响应速度变慢。而异步编程则通过异步回调、Promise、async/await等方式，使得程序可以在等待时间中继续执行其他操作，从而提高程序的响应速度和并发能力。

在异步编程中，通过将需要等待的操作封装成异步任务，异步任务在执行时不会阻塞程序的主线程，而是在任务完成后会通过回调、Promise等方式通知程序执行相关的操作。异步编程通常涉及到事件循环、回调函数、Promise对象等概念。

异步编程的优点包括：

- 提高程序的响应速度：在异步编程中，等待时间不会阻塞程序的主线程，从而可以提高程序的响应速度。
- 提高程序的并发能力：异步编程中可以并行执行多个任务，从而提高程序的并发能力。
- 提高程序的可维护性：通过异步编程可以将代码分离成更小的模块，从而提高代码的可维护性。
- 降低代码的复杂度：异步编程可以降低代码的复杂度，避免了嵌套回调等编程方式带来的代码难度。
需要注意的是，在异步编程中也会带来一些问题，例如回调地狱、Promise的错误处理等，因此需要谨慎使用异步编程，合理选择异步编程方式，并注意对异步任务的错误处理和异常情况的处理。



## CSR 与 SSR

CSR 和 SSR 是两种常用的前端渲染方式，它们有以下区别：

客户端渲染（CSR）：浏览器首先加载一个 HTML 页面，然后在页面加载完成后，通过 JavaScript 请求数据并生成页面内容。这种方式的优点是可以减轻服务器压力，缺点是页面加载速度较慢，首次加载时可能需要一定的等待时间，并且对于搜索引擎优化（SEO）不太友好。
服务端渲染（SSR）：服务器端在接收到请求后，直接返回渲染好的 HTML 页面，而不是一个空白的 HTML 页面。这种方式的优点是页面加载速度快，首屏渲染速度快，对于 SEO 较为友好，缺点是需要增加服务器压力，因为服务器需要在每次请求时重新生成 HTML 页面。

在 CSR 中，页面渲染交由客户端 JavaScript 处理，客户端 JavaScript 会请求数据并生成 HTML 页面，这会造成初始加载时需要等待请求和渲染的时间。在 CSR 中，搜索引擎爬虫无法抓取到页面的完整内容，因为页面渲染的过程需要等待客户端 JavaScript 请求数据并生成 HTML 页面。这也是 CSR 的一个缺点，因为它对 SEO 不太友好。  
在 SSR 中，服务器会在收到请求后，将 HTML 页面直接渲染好并返回给浏览器，因此，客户端只需要等待数据的请求和传输时间。这使得 SSR 可以实现更快的首次加载时间，因为浏览器无需等待 JavaScript 和数据的请求和渲染时间。并且，因为页面已经完全渲染完成并返回给浏览器，搜索引擎爬虫可以直接抓取完整的 HTML 内容，提高了 SEO 的效果。但是，SSR 也有它的缺点。每个页面的请求都需要额外的服务器端处理时间和资源，这可能会导致服务器负载增加。此外，与 CSR 不同，SSR 应用程序可能不太容易缓存，因为每个页面都需要进行渲染和传输。


## SSR
### 对 SSR 有了解吗，它主要解决什么问题？

Server-Side Rendering 我们称其为 SSR，意为服务端渲染指由服务侧完成页面的 HTML 结构拼接的页面处理技术，发送到浏览器，然后为其绑定状态与事件，成为完全可交互页面的过程；

解决了以下两个问题：

seo：搜索引擎优先爬取页面 HTML 结构，使用 ssr 时，服务端已经生成了和业务想关联的 HTML，有利于 seo
首屏呈现渲染：用户无需等待页面所有 js 加载完成就可以看到页面视图（压力来到了服务器，所以需要权衡哪些用服务端渲染，哪些交给客户端）
缺点

复杂度：整个项目的复杂度
性能会受到影响
服务器负载变大，相对于前后端分离务器只需要提供静态资源来说，服务器负载更大，所以要慎重使用

### SSR 优缺点？

- 优点：
  - SSR 有着更好的 SEO（搜索引擎优化）、并且首屏加载速度更快。
- 缺点：
  - 开发条件会受限制，服务器端渲染只支持 beforeCreate 和 created 两个钩子，当我们需要一些外部扩展库时需要特殊处理，服务端渲染应用程序也需要处于 Node.js 的运行环境。
    服务器会有更大的负载需求。

PWA (Progressive Web App) 是渐进式 Web 应用的缩写，是一种 Web 应用的开发模式。它的目标是让 Web 应用具备类似原生应用的体验，可以脱离浏览器单独运行，即使在没有网络连接的情况下也能够使用。PWA 基于现代 Web 技术，包括 Service Worker、Web App Manifest、HTTPS 等，通过提高 Web 应用的可靠性、快速加载和可离线访问性等方面，来提升用户的体验。

PWA 应用具备以下特点：

- 可以像原生应用一样安装在移动设备或电脑上，可以像原生应用一样从桌面启动。
- 在不同的网络条件下都可以正常工作，包括在没有网络的情况下。
- 可以获得更快的加载速度，因为 PWA 应用可以通过 Service Worker 预缓存应用所需的资源，从而加快加载速度。
- 可以获得更好的用户体验，因为 PWA 应用可以实现类似原生应用的交互效果，包括无延迟的响应、离线使用、推送通知等。
PWA 技术的出现，使得 Web 应用与原生应用之间的界限越来越模糊，有望成为未来 Web 应用的主流开发方式。

### import()
import() 是 ES6 模块系统中的一种动态导入方式，可以在运行时动态地加载模块。它返回一个 Promise 对象，Promise 对象的 then 方法里面提供了加载的模块对象。

与静态导入不同，import() 是一种动态加载的方式，只有在代码运行到 import() 语句时，才会去加载指定的模块。这种方式适合于按需加载或者延迟加载一些模块，以优化页面加载速度和性能。

import() 还支持传入表达式作为参数，以实现根据不同情况动态加载不同的模块。此外，它还可以与 async/await 结合使用，让代码更加简洁和易于理解。

### jt 奇淫巧计

- 双问号（??）运算符：该运算符可以用来提供默认值，如果左侧操作数的值为 null 或 undefined，则返回右侧操作数的值，否则返回左侧操作数的值。
- 双感叹号（!!）运算符：该运算符可以将任何值转换为布尔值，如果值是 falsy（例如 null、undefined、0、''、NaN），则返回 false，否则返回 true。
- Array.from() 方法：该方法可以将类数组对象或可迭代对象转换为数组，例如将 NodeList 对象转换为数组。
- 扩展运算符（...）：该运算符可以用来展开数组或对象，例如将一个数组转换为函数的参数列表，或将一个对象合并到另一个对象中。
- 函数柯里化：柯里化是一种将函数转换为一系列接受单个参数的函数的技术，每个函数都返回一个新函数，该新函数期望下一个参数。柯里化可以让函数更灵活、更可重用。
- 箭头函数：箭头函数是一种更简洁的函数定义语法，可以使用单个表达式来定义函数体，并且自动绑定 this 关键字到定义函数的上下文。

###  WebAssembly 
WebAssembly（简称Wasm）是一种可以在现代 Web 浏览器中运行的低级字节码。它的目标是成为一种新的可移植的目标语言，为编写高性能的 Web 应用程序提供一种通用方法。Wasm 能够在浏览器中运行，使得开发者能够在不依赖特定平台或语言的情况下，实现高性能的 Web 应用程序。Wasm 的二进制格式是一种基于栈的虚拟机表示形式，其执行速度比 JavaScript 更快

### 快速上手 WebAssembly
可以遵循以下步骤：

- 了解 WebAssembly 的基础知识：WebAssembly 是一种二进制格式的可移植代码，它可以在现代 Web 浏览器中运行。WebAssembly 代码可以使用几种不同的语言编写，如 C、C++、Rust 等。WebAssembly 通过减少代码大小和优化执行速度，使 Web 应用程序更快和更安全。
- 选择编程语言：WebAssembly 可以使用多种编程语言编写。其中，C、C++ 和 Rust 是最常见的选择。这些语言可以通过编译器将源代码编译为 WebAssembly 字节码。
- 编写 WebAssembly 模块：使用选择的编程语言编写 WebAssembly 模块，并使用编译器将其编译为 WebAssembly 字节码。
- 将 WebAssembly 模块嵌入到 Web 应用程序中：WebAssembly 模块可以通过 JavaScript 代码加载和使用。可以使用 JavaScript 模块加载器或者自己编写 JavaScript 代码来加载 WebAssembly 模块。
- 在 Web 应用程序中使用 WebAssembly 模块：一旦加载了 WebAssembly 模块，可以使用 JavaScript 与 WebAssembly 模块进行交互。例如，可以调用 WebAssembly 模块中的函数，并使用 JavaScript 代码来处理返回的结果。

总的来说，WebAssembly 是一种强大的技术，可以在 Web 应用程序中加速代码执行速度。虽然 WebAssembly 还是一个新技术，但已经得到了广泛的支持，并且在不断发展。对于想要了解 WebAssembly 的开发人员，上述步骤提供了一个很好的起点。

什么是 WebAssembly？它是用来做什么的？
WebAssembly 有哪些主要的优点？
WebAssembly 和 JavaScript 有什么区别？它们可以互相调用吗？
WebAssembly 模块是如何加载和使用的？
WebAssembly 支持哪些编程语言？
WebAssembly 的二进制格式是什么样子的？
WebAssembly 实现了哪些基本的数据类型？
WebAssembly 支持哪些标准的操作指令？
WebAssembly 模块是如何与 JavaScript 代码交互的？
WebAssembly 在哪些场景下特别有用？


### 函数柯里化（Currying）
是指将一个接收多个参数的函数转化为接收一个单一参数的函数，并且返回一个新的函数，该新函数可以接收余下的参数来返回结果。这种转换可以用于许多编程语言和范式中，包括JavaScript和函数式编程。

```js 
function add(x) {
  return function(y) {
    return x + y;
  };
}

// 调用函数
add(2)(3); // 输出 5
```
:::info 
函数柯里化常用于实现函数的复用和延迟执行。例如，如果我们需要多次调用一个函数并传入相同的参数，我们可以先柯里化该函数，然后再使用柯里化后的函数进行多次调用。另外，如果我们需要在某个时间点才执行某个函数，也可以使用柯里化来实现延迟执行。
:::

### Symbol有什么用

Symbol 是 ES6 新增的基本数据类型，它表示独一无二的值。Symbol 值通过 Symbol 函数生成，使用方法类似于其他内置的基本类型，例如 String、Number 等。

Symbol 的主要作用是创建对象的唯一属性名，因为它生成的值是唯一的，所以可以避免属性名冲突的问题。Symbol 值可以用作对象的属性名，用 Symbol 值作为属性名，这个属性不会出现在 for...in、Object.keys()、JSON.stringify() 等遍历对象属性的方法中，但是它并不是私有属性，可以通过 Object.getOwnPropertySymbols() 方法获取到。

除了作为对象属性名外，Symbol 还有一些其他的应用场景，例如实现类似于迭代器的操作，或者用作一些特殊用途的标识符等。

总之，Symbol 的主要作用是创建独一无二的值，可以用作对象的属性名，避免属性名冲突。


### 什么是事件冒泡和事件捕获?
事件冒泡和事件捕获是两种不同的事件传播机制。

事件冒泡指的是事件在从子元素向父元素逐级传递的过程中，依次触发父元素上的相应事件。也就是说，当一个子元素上的事件被触发后，它的父元素上绑定的同类事件也会被依次触发，一直到根元素为止。事件冒泡是默认的事件传播机制。

事件捕获则是从根元素开始，逐级向子元素传递事件，直到最终到达触发事件的目标元素。也就是说，当一个子元素上的事件被触发后，它的父元素上绑定的同类事件会在捕获阶段触发，而子元素上绑定的同类事件则在冒泡阶段触发。

在实际开发中，可以通过 addEventListener 方法的第三个参数来选择事件传播机制，设置为 true 表示事件捕获，设置为 false 或不设置则表示事件冒泡。



### 你如何理解 Web 标准？
Web 标准是指由万维网联盟（W3C）等标准化组织所制定的一系列规范和标准，包括 HTML、CSS、JavaScript 等在内的多项技术规范。这些规范旨在确保 Web 技术的互用性和可持续性，使得在不同的浏览器和平台上，Web 应用程序都能正常运行。

Web 标准可以分为三个方面：结构（Structure）、样式（Presentation）和行为（Behavior）。其中，结构标准指 HTML 和 XML，样式标准指 CSS，行为标准指 DOM 和 JavaScript。

遵循 Web 标准能够使得 Web 应用程序具有更好的可维护性、可扩展性、可访问性和可用性，并且在不同的浏览器和平台上都能够获得相同的体验，同时也能够提高 Web 应用程序的安全性和稳定性。

### 什么是 Web Workers？在什么情况下你会使用它？
Web Workers 是一个在浏览器中运行的 JavaScript 脚本，能够在后台执行一些耗时的任务而不影响页面的交互和性能。

在 Web 应用中，JavaScript 程序通常是单线程运行的，也就是说它们不能同时执行多个任务。这意味着当你在执行某个长时间运行的任务时，整个页面会被阻塞，直到这个任务完成。如果你的应用中需要执行一些复杂或耗时的操作，这会导致用户体验下降。

Web Workers 解决了这个问题，允许开发者在后台线程中运行 JavaScript 程序，以免阻塞主线程。在 Web Workers 中，你可以使用一些基本的 API 来执行各种任务，例如计算、网络请求等。

Web Workers 适合用于一些需要进行大量计算或 I/O 操作的任务，比如图像处理、音频处理等。当你需要执行这些任务时，你可以将它们分配给一个 Web Worker，让它在后台运行，同时保持主线程的交互性能。

### 解释一下你理解的前端安全性。
- 前端安全性是指保护 Web 应用程序和用户免受恶意攻击和数据泄露的能力。在前端开发中，需要注意以下几个方面的安全问题：
  - 跨站脚本攻击（XSS）：攻击者利用漏洞将恶意脚本注入到页面中，获取用户信息，窃取 Cookie 等。
  - 跨站请求伪造（CSRF）：攻击者利用伪装的表单提交、图片 URL 等方式，让用户在不知情的情况下发送请求。
  - 点击劫持：攻击者将透明的 iframe 覆盖在页面上，覆盖在用户希望点击的位置，达到欺骗用户的目的。
  - 资源劫持：攻击者通过窃取 JavaScript、CSS、图片等资源来欺骗用户或在其浏览器中注入恶意代码。
  - 不安全的通信：数据在传输过程中被窃取或篡改，例如 HTTP 协议的明文传输。
- 为了提高前端应用的安全性，需要注意以下几点：
  - 输入合法性检查：对用户输入的内容进行过滤和验证，避免恶意输入引起的安全问题。
  - 服务端验证：前端校验只是对用户友好的提示，后端需要进行最终验证，避免数据在传输过程中被篡改。
  - 防范 XSS 攻击：避免使用 innerHTML、eval 等动态生成代码的方法，将用户输入的内容进行编码。
  - 防范 CSRF 攻击：在关键请求中加入随机的 token，验证请求的来源。
  - 使用 HTTPS：通过加密传输数据，防止数据在传输过程中被篡改和窃取。

### 什么是 MVVM？它与 MVC 的区别是什么？
MVVM是一种前端架构模式，它的名称代表了Model-View-ViewModel。它将应用程序分为三个部分：Model（数据层）、View（视图层）和ViewModel（连接器），并以一种相对松散的方式连接它们。

与MVC模式相比，MVVM模式的主要区别在于ViewModel的引入，它负责将Model中的数据转换为View中的数据，并且将View中的事件转换为对Model的操作。在MVC模式中，控制器（Controller）负责将Model数据转换为View中的数据，但是它不同于MVVM模式中的ViewModel，因为它不能处理View中的事件。

在MVVM模式中，ViewModel是一个中间层，它不仅仅只是一个数据转换器，还可以包含与View相关的逻辑，例如表单验证和数据格式化等。ViewModel还可以包含与Model相关的逻辑，例如数据查询和持久化等。这种结构使得ViewModel可以在不依赖View的情况下进行单元测试，同时也使得View与Model之间的耦合度降低。

总之，MVVM模式通过引入ViewModel，为前端开发提供了更加清晰和松散的架构模式，使得应用程序的各个部分更加独立，更容易维护和测试。

### 解释一下 JavaScript 中的原型链以及如何利用原型链实现继承。
在 JavaScript 中，每个对象都有一个指向其原型对象的内部链接，这个链接被称为原型链。原型链允许对象继承其原型对象的属性和方法。

当访问一个对象的属性或方法时，如果该对象自身没有定义这个属性或方法，JavaScript 引擎就会沿着原型链往上查找，直到找到该属性或方法或者到达原型链的顶端（也就是 Object.prototype）为止。

可以使用 Object.create 方法来创建一个新的对象，同时将其原型链接到另一个对象上，从而实现继承。例如：

```js 
var parent = {
  name: "Parent",
  sayHello: function() {
    console.log("Hello from " + this.name);
  }
};

var child = Object.create(parent);
child.name = "Child";
child.sayHello(); // 输出 "Hello from Child"

```
在这个例子中，child 对象继承了 parent 对象的属性和方法，并且可以通过原型链访问到这些属性和方法。当调用 child.sayHello() 时，this 的值是 child 对象本身，因此输出的是 "Hello from Child"。

需要注意的是，在 JavaScript 中，除了使用原型链继承之外，还可以使用构造函数和 class 关键字来实现继承。每种继承方式都有其优缺点，具体使用哪种方式取决于具体的需求和情况。
### 请解释一下什么是异步编程？有哪些实现异步编程的方式？
异步编程是一种编程方式，其中代码可以在等待某些操作完成时继续执行，而不必阻塞并等待这些操作完成。这种方式可以提高代码的效率和响应性，因为它允许代码在等待慢速操作（如文件读写或网络请求）的同时执行其他任务。

- 实现异步编程的方式有多种，包括：
  - 回调函数：将函数作为参数传递给其他函数，在操作完成后调用回调函数。
  - Promise：使用 Promise 对象表示异步操作的状态和结果，可以通过 then() 方法注册成功和失败的回调函数。
  - async/await：使用 async 和 await 关键字可以使异步代码看起来像同步代码，使其更易于编写和理解。
  - 事件监听器：使用事件监听器注册操作完成时调用的函数，然后继续执行其他任务。
  - 发布/订阅模式：使用发布/订阅模式，使得对象或函数可以观察其他对象或函数，并在操作完成时调用观察者的回调函数。
这些方法可以单独使用，也可以组合使用，根据具体的应用场景选择最合适的方式实现异步编程。

### 请描述一下浏览器是如何解析 HTML 文件的？
- 浏览器解析 HTML 文件的过程可以分为以下几个步骤：
  - 构建 DOM 树：浏览器将 HTML 文件解析成一个 DOM 树，DOM 树的根节点就是 html 标签，它的子节点包括 head 和 body 标签。
  - 构建 CSSOM 树：浏览器将 CSS 文件解析成一个 CSSOM 树，CSSOM 树包括样式规则和样式声明。
  - 合并 DOM 树和 CSSOM 树，生成渲染树：浏览器将 DOM 树和 CSSOM 树合并，生成一个渲染树。渲染树中只包含需要显示的元素和这些元素的样式信息。
  - 布局和绘制渲染树：浏览器对渲染树进行布局和绘制。布局确定每个元素在页面上的位置，绘制则将元素的内容呈现到屏幕上。
在这个过程中，如果浏览器遇到 JavaScript 代码或者样式表，就会停止渲染，执行 JavaScript 代码或者下载样式表，等 JavaScript 代码执行完毕或者样式表下载完毕后，再继续渲染。


### iterator 是什么？

在 JavaScript 中，iterator 是一个对象，它提供了一种用于访问集合元素的方式。通过迭代器对象，可以依次访问集合中的每个元素，而无需暴露集合的底层表示方式。

迭代器的核心方法是 next()，调用 next() 方法会返回一个对象，该对象包含两个属性：value 和 done。value 属性表示当前迭代的元素的值，done 属性表示是否已经迭代完集合中的所有元素。

迭代器在 ES6 中被广泛应用，例如在 for...of 循环中，就是通过迭代器来遍历集合中的元素的。此外，Map、Set、Array 等数据结构都实现了迭代器接口，可以通过迭代器来访问集合中的元素。

### 副作用是什么意思?
在计算机科学中，函数的副作用是指函数执行期间对系统环境所作的修改。副作用可能是无意的，例如写入文件的函数可能会意外地更改文件的内容。但是副作用也可以是有意的，例如修改变量的值，打印输出等。

在 JavaScript 中，函数的副作用通常包括修改全局变量、修改传递给函数的对象、进行 I/O 操作、引发异常等。函数的副作用可能会影响到代码的可维护性、可测试性以及程序的正确性。

编写无副作用的代码可以提高程序的可读性、可测试性和健壮性。因此，在编写 JavaScript 代码时，需要尽可能地减少函数的副作用，这就是函数式编程的思想所倡导的。


## Cookie 和 Session区别
Cookie 和 Session 都是 Web 应用程序中用于存储用户状态信息的机制，但它们之间有以下几个区别：


- 存储位置不同：Cookie 存储在客户端浏览器中，而 Session 存储在服务器上。
- 安全性不同：Cookie 可以被客户端浏览器禁用或删除，也可以被篡改和伪造，因此存储在 Cookie 中的敏感数据应进行加密。而 Session 存储在服务器上，客户端无法直接访问，因此相对来说更加安全。
- 大小限制不同：Cookie 的大小通常限制在 4KB 左右，而 Session 没有明确的大小限制，但过大的 Session 会占用服务器资源。
- 生命周期不同：Cookie 可以设置一个过期时间，也可以设置为浏览器关闭时自动删除，而 Session 的生命周期通常由服务器控制，可以设置为一定的时间或者在用户退出登录时销毁。

综上所述，Cookie 适用于存储一些较小、不敏感的数据，如用户偏好设置，而 Session 适用于存储敏感数据和需要保证安全性的信息，如用户登录状态等。


## 编译时 运行时
编译时指的是将源代码（比如Java或C++代码）转换成机器码或字节码的过程。在编译时，编译器会检查源代码的语法和语义，生成目标代码，并将其保存到磁盘中以备后续的执行。编译后的程序可以直接在计算机上运行，而不需要再次编译。

运行时指的是程序在计算机上实际执行的阶段。在运行时，计算机会读取目标代码，将其加载到内存中，并执行其中的指令。在执行过程中，程序会访问计算机的内存和其他资源，并根据输入和程序逻辑产生输出。

在前端开发中，编译时和运行时的概念也适用。比如，在Vue.js中，模板的编译就是一个编译时的过程，它将模板转换为可执行的JavaScript代码，用于在运行时生成实际的DOM元素。而在React中，JSX语法的编译也是一个编译时的过程，它将JSX转换为JavaScript代码，用于在运行时生成React组件。

总的来说，编译时和运行时是程序执行的两个阶段，它们都是程序的重要组成部分，对程序的性能和功能都有着重要的影响。

## Fetch 的基本使用

## js reduce 

## defer和async的区别主要有以下几点：
```html
defer：在浏览器解析 HTML 文件时，遇到 defer 关键字的 <script> 标签时，会将其放入文档的底部，等到所有元素解析完成后再执行。也就是说，defer 脚本会在文档解析完毕后按照它们在文档中出现的顺序依次执行，但在 DOMContentLoaded 事件之前完成。
async：在浏览器解析 HTML 文件时，遇到 async 关键字的 <script> 标签时，会异步加载该脚本，并在加载完成后立即执行。也就是说，async 脚本的执行顺序不能保证，可能会在页面元素解析完成之前或之后执行。
```


## 有一个div，在不滚动的情况下垂直水平居中，页面滚动的时候会随着滚动。到顶部的时候固定。怎么实现.
可以使用 CSS position 属性和 JavaScript 来实现这个效果。让这个 div 在不滚动的情况下垂直水平居中，可以使用以下样式:

```css
div {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
```

接下来，当页面滚动时，我们需要动态地改变 div 的位置。可以使用 JavaScript 来监听滚动事件，并根据滚动距离来计算 div 的位置。

```js 
const div = document.querySelector('div');
const divRect = div.getBoundingClientRect(); // 获取 div 的位置和大小信息
const offsetTop = divRect.top + window.pageYOffset; // div 距离页面顶部的距离

window.addEventListener('scroll', function() {
  const scrollTop = window.pageYOffset; // 获取滚动距离
  if (scrollTop >= offsetTop) {
    div.style.position = 'fixed';
    div.style.top = '0';
    div.style.left = '50%';
    div.style.transform = 'translateX(-50%)';
  } else {
    div.style.position = 'absolute';
    div.style.top = '50%';
    div.style.left = '50%';
    div.style.transform = 'translate(-50%, -50%)';
  }
});
```
在滚动事件中，我们先获取 div 的位置信息，并计算出它距离页面顶部的距离。当滚动距离超过这个距离时，我们将 div 的 position 属性设为 fixed，并将 top 设为 0，left 设为 50%，然后通过 transform 属性将 div 水平居中。当滚动距离小于这个距离时，我们将 div 的 position 属性设为 absolute，并将 top 和 left 设为 50%，然后通过 transform 属性将 div 垂直水平居中。

最后，这个 div 就可以在不滚动的情况下垂直水平居中，而且可以随着页面滚动并固定在顶部。


## 单项链表，判断左右对称
- 遍历链表，找到链表的中间节点。  
- 反转链表的后半部分。  
- 依次比较链表的前半部分和反转后的后半部分，判断它们的节点值是否相等。

如果链表的长度为奇数，则中间节点不需要参与比较；如果链表的长度为偶数，则需要将中间两个节点分别分配给前半部分和后半部分。

```js 
function isListSymmetric(head) {
  if (!head || !head.next) {  // 空链表或只有一个节点的链表一定对称
    return true;
  }

  // 找到链表的中间节点
  let slow = head, fast = head.next;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
  }

  // 反转链表的后半部分
  let prev = null, cur = slow.next;
  slow.next = null;
  while (cur) {
    const next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
  }

  // 比较链表的前半部分和反转后的后半部分
  let p1 = head, p2 = prev;
  while (p1 && p2) {
    if (p1.val !== p2.val) {
      return false;
    }
    p1 = p1.next;
    p2 = p2.next;
  }

  return true;
}
```

首先判断链表是否为空或只有一个节点，这些情况下链表一定对称。然后通过快慢指针找到链表的中间节点，然后反转链表的后半部分。最后依次比较链表的前半部分和反转后的后半部分即可。


## DOS、 DDOS攻击原理和防范
DOS（Denial of Service）攻击和 DDOS（Distributed Denial of Service）攻击都是一种网络攻击方式，其原理和防范措施如下：

DOS攻击原理

DOS攻击是通过向目标系统发送大量的请求来占用其资源，导致系统无法正常服务，甚至崩溃的攻击方式。这些请求可能来自单个计算机或多个计算机，攻击者通过发送大量的请求使目标系统超负荷工作，导致其无法响应正常的请求。

DDOS攻击原理

DDOS攻击是通过利用多台计算机或其他设备共同协作，向目标系统发送大量的请求，使目标系统无法正常服务，从而达到瘫痪目标系统的目的。攻击者通常使用“僵尸网络”来发起攻击，将大量的计算机感染成“僵尸”并控制它们来发送请求，从而形成大规模的攻击。

DOS/DDOS攻击防范措施

增加带宽：攻击者的攻击是要占用目标系统的带宽资源，增加带宽能够增加系统对攻击的承受能力。
过滤非法流量：通过流量过滤器来识别非法的请求流量，如IP黑名单，流量限制，HTTP请求过滤等。
负载均衡：通过负载均衡将请求分散到多台服务器上，使得攻击者无法集中攻击单一服务器，从而分散攻击力。
增强安全性：及时更新系统补丁、强化网络设备和系统的安全策略，加强安全审计等措施来增强系统的安全性。
CDN加速：通过CDN技术，将请求转发到最近的服务器，缓存请求结果，使得请求得以快速响应，从而有效防止攻击。
智能防火墙：利用智能防火墙的技术，可以实时识别攻击流量，尽早预警和阻拦攻击。


## 参考

[property 和 attribute 区别](https://www.cnblogs.com/lmjZone/p/8760232.html)

